﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Swiss
{
    using SECT = UInt32;
    using FSINDEX = UInt32;
    using DFSIGNATURE = UInt32;
    //using CLSID = Byte[];

    /// <summary>
    /// Wraps low level (read-only access) to Structured Storage Files.
    /// </summary>
    public class StorageLowLevel
    {

        const SECT DIFSECT = 0xFFFFFFFC;    //[4 bytes] 
        const SECT FATSECT = 0xFFFFFFFD;    //[4 bytes] 
        const SECT ENDOFCHAIN = 0xFFFFFFFE; //[4 bytes] 
        const SECT FREESECT = 0xFFFFFFFF;   //[4 bytes]

        /// <summary>
        /// The Header of a Structured Storage.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 512)]
        public struct StructuredStorageHeader
        {
            // [offset from start in bytes, length in bytes] 

            /// <summary>
            /// [000H,08] {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1} for current version, 
            /// was {0x0e, 0x11, 0xfc, 0x0d, 0xd0, 0xcf, 0x11, 0xe0} on old, beta 2 files (late ’92) 
            /// which are also supported by the reference implementation
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] _abSig;

            /// <summary>
            /// [008H,16] class id (set with WriteClassStg, retrieved with GetClassFile/ReadClassStg) 
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] clid;

            /// <summary>
            /// [018H,02] minor version of the format: 33 is written by reference implementation
            /// </summary>
            public ushort _uMinorVersion;

            /// <summary>
            /// [01AH,02] major version of the dll/format: 3 is written by reference implementation 
            /// </summary>
            public ushort _uDllVersion;

            /// <summary>
            /// [01CH,02] 0xFFFE: indicates Intel byte-ordering 
            /// </summary>
            public ushort _uByteOrder;

            /// <summary>
            /// [01EH,02] size of sectors in power-of-two (typically 9, indicating 512-byte sectors) 
            /// </summary>
            public ushort _uSectorShift;

            /// <summary>
            /// [020H,02] size of mini-sectors in power-of-two (typically 6, indicating 64-byte mini-sectors)
            /// </summary>
            public ushort _uMiniSectorShift;

            /// <summary>
            /// [022H,02] reserved, must be zero
            /// </summary>
            public ushort _usReserved;

            /// <summary>
            /// [024H,04] reserved, must be zero
            /// </summary>
            public uint _ulReserved1;

            /// <summary>
            /// [028H,04] reserved, must be zero 
            /// </summary>
            public uint _ulReserved2;

            /// <summary>
            /// [02CH,04] number of SECTs in the FAT chain
            /// </summary>
            public FSINDEX _csectFat;

            /// <summary>
            /// [030H,04] first SECT in the Directory chain
            /// </summary>
            public SECT _sectDirStart;

            /// <summary>
            /// [034H,04] signature used for transactionin: must be zero. The reference implementation does not support transactioning 
            /// </summary>
            public DFSIGNATURE _signature;

            /// <summary>
            /// [038H,04] maximum size for mini-streams: typically 4096 bytes
            /// </summary>
            public uint _ulMiniSectorCutoff;

            /// <summary>
            /// [03CH,04] first SECT in the mini-FAT chain 
            /// </summary>
            public SECT _sectMiniFatStart;

            /// <summary>
            /// [040H,04] number of SECTs in the mini-FAT chain 
            /// </summary>
            public FSINDEX _csectMiniFat;

            /// <summary>
            /// [044H,04] first SECT in the DIF chain 
            /// </summary>
            public SECT _sectDifStart;

            /// <summary>
            /// [048H,04] number of SECTs in the DIF chain 
            /// </summary>
            public FSINDEX _csectDif;

            /// <summary>
            /// [04CH,436] the SECTs of the first 109 FAT sectors
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 109)]
            public SECT[] _sectFat;

            /// <summary>
            /// Returns true if the storage has either one of the signatures.
            /// </summary>
            public Boolean isStorage
            {
                get
                {
                    //: 0x49 0x54 0x53 0x46 0x3 0x0 0x0 0x0
                    Console.WriteLine("Signature: " +
                        "0x{0:x} 0x{1:x} 0x{2:x} 0x{3:x} 0x{4:x} 0x{5:x} 0x{6:x} 0x{7:x}",
                        _abSig[0], _abSig[1], _abSig[2], _abSig[3], _abSig[4], _abSig[5], _abSig[5], _abSig[7]);

                    return (false
                        || (                    //0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1
                       _abSig[0] == 0xD0
                    && _abSig[1] == 0xCF
                    && _abSig[2] == 0x11
                    && _abSig[3] == 0xE0
                    && _abSig[4] == 0xA1
                    && _abSig[5] == 0xB1
                    && _abSig[6] == 0x1A
                    && _abSig[7] == 0xE1)
                    || (                        //0x0e, 0x11, 0xfc, 0x0d, 0xd0, 0xcf, 0x11, 0xe0 (Late '92 Beta 2)
                       _abSig[0] == 0x0E
                    && _abSig[1] == 0x11
                    && _abSig[2] == 0xFC
                    && _abSig[3] == 0x0D
                    && _abSig[4] == 0xD0
                    && _abSig[5] == 0xCF
                    && _abSig[6] == 0x11
                    && _abSig[7] == 0xE0)
                    );
                }
            }

            /// <summary>
            /// Returns true if the storage has the Late '92 Beta 2 Signature.
            /// </summary>
            public Boolean isBeta2
            {
                get
                {
                    return (isStorage && _abSig[0] == 0x1E);
                }
            }

            /// <summary>
            /// Initializes the sturcture.            
            /// </summary>
            /// <param name="init">Ignored</param>
            public StructuredStorageHeader(Boolean init)
            {
                _abSig = new byte[8];
                clid = new byte[16];
                _uMinorVersion = 0;
                _uDllVersion = 0;
                _uByteOrder = 0;
                _uSectorShift = 0;
                _uMiniSectorShift = 0;
                _usReserved = 0;
                _ulReserved1 = 0;
                _ulReserved2 = 0;
                _csectFat = 0;
                _sectDirStart = 0;
                _signature = 0;
                _ulMiniSectorCutoff = 0;
                _sectMiniFatStart = 0;
                _csectMiniFat = 0;
                _sectDifStart = 0;
                _csectDif = 0;
                _sectFat = new SECT[109];
            }

            /// <summary>
            /// Sector Size.
            /// </summary>
            public Int32 SectorSize
            {
                get
                {
                    return 1 << _uSectorShift;
                }
            }

            /// <summary>
            /// Mini Sector Size.
            /// </summary>
            public Int32 MiniSectorSize
            {
                get
                {
                    return 1 << _uMiniSectorShift;
                }
            }

            /// <summary>
            /// Storage Format Version.
            /// </summary>
            public String FormatVersion
            {
                get
                {
                    return String.Format("{0}.{1}", _uDllVersion, _uMinorVersion);
                }
            }

            /// <summary>
            /// Byte Ordering.
            /// </summary>
            public Boolean IntelByteOrdering
            {
                get
                {
                    return
                        _uByteOrder == 0xFFFE;
                }
            }
        }
    }
}
